Skip to content

feat(codex): Codex command group + plugin install hardening#52

Merged
chenliuyun merged 36 commits into
mainfrom
feat/codex-commands
May 23, 2026
Merged

feat(codex): Codex command group + plugin install hardening#52
chenliuyun merged 36 commits into
mainfrom
feat/codex-commands

Conversation

@chenliuyun
Copy link
Copy Markdown
Collaborator

Summary

This branch lands the full Codex integration workstream that was sitting under [Unreleased] in CHANGELOG.md. Highlights:

  • switchbot codex command groupsetup, doctor, repair orchestrate plugin install, the 7-check health summary, and end-to-end repair. switchbot install --agent codex is the register-only sibling.
  • codex setup 6-step flowinstall-switchbot-cliinstall-codex-pluginregister-pluginauthdoctor-verify. A single npx @switchbot/openapi-cli codex setup bootstraps a brand-new machine end-to-end. Both install steps are skippable via --skip.
  • Monorepo absorption@switchbot/codex-plugin (formerly @cly-org/switchbot-codex-plugin) now ships from this repo under packages/codex-plugin/. A single GitHub Release publishes any package whose version was bumped.
  • onInstall hook hardening — best-effort, always exits 0 so a missing/broken switchbot CLI never rolls back the Codex plugin install. When the CLI is present it runs switchbot codex setup --yes; when absent it prints a hint pointing at npx @switchbot/openapi-cli codex setup.
  • Codex register-plugin on Windowscodex plugin marketplace add from <npm-root>/@switchbot/codex-plugin failed because Codex 0.133.0 misparses paths containing @. Registration now bridges via a junction at %LOCALAPPDATA%\switchbot\codex-plugin-marketplace (fallback ~/.switchbot/codex-plugin-marketplace); divergent junctions are repaired in place, real directories at the alias path raise an error instead of silently falling back to the broken @ path.
  • CI — per-package publish matrix with detect-versions gate; per-package smoke matrix (CLI offline + live; plugins tarball-shape only); sibling policy-schema-sync removed since the skill consumer is now in this monorepo.

See CHANGELOG.md [Unreleased] for the full bullet list.

Test Plan

  • npm test — 2723/2723 across 143 files
  • npm test --workspace @switchbot/codex-plugin — 39/39 (5 new cases for the alias logic)
  • npm run smoke:codex-pack-install — tarball install + setup dry-run shape + non-blocking onInstall hook
  • Manual stale-junction repair on Windows (planted a junction pointing at TEMP, ran resolveMarketplaceSourceRoot directly, junction repointed to real packageRoot)
  • CI green on this branch

chenliuyun added 30 commits May 23, 2026 09:23
…elper

- doctor-verify in both specs now spells out the same 7-check set
  (4 base + 3 codex), so codex setup and codex repair share semantics.
- auth/re-auth argv documents --profile and --config forwarding via the
  buildAuthLoginArgv helper, matching codex repair re-auth.
- onInstall hook path pinned to packages/codex-plugin/bin/auth.js per
  current .codex-plugin/hooks.json (no more "(or install.js)" wording).
- stepRegisterCodexPlugin / repairStepRegisterPlugin / setup
  register-plugin all reference the shared registerCodexPlugin() helper;
  inline npm root -g and pluginId concat are forbidden.
…helper

- preflight: --agent codex now fails (was warn) when the npm package is
  missing — this is a register-only command, so a missing package is a
  hard stop, not a soft hint.
- codex-checks: warning text for missing/unregistered plugin now spells
  out the full repair recipe (npm install -g @cly-org/switchbot-codex-plugin
  && switchbot install --agent codex) instead of just the latter half.
- codex-checks: add registerCodexPlugin() helper that wraps
  resolveCodexPackageRoot + resolvePluginId + runCodexPluginRegistration
  with normalized error strings, so the three call sites
  (install --agent codex, codex repair, codex setup) share one path.
- default-steps.stepRegisterCodexPlugin: replace the inlined npm root -g
  + path.join + runCodexPluginRegistration with a single
  registerCodexPlugin() call.
- doctor: export formatDoctorChecks for reuse by codex doctor / repair /
  setup output formatting.
…ed helper

- New `switchbot codex setup` subcommand runs five steps:
  check-codex-cli, install-switchbot-cli, register-plugin, auth,
  doctor-verify (4 base + 3 codex = 7 checks). Only install-switchbot-cli
  and auth are skippable; --skip on other steps exits 2.
- codex repair re-auth and codex setup auth share buildAuthLoginArgv,
  which forwards the active --profile and --config to the spawned
  `auth login`. Spawn now uses process.execPath + cliPath
  (process.argv[1]) instead of resolving "switchbot" through PATH.
- repairStepRegisterPlugin replaced with a single registerCodexPlugin()
  call so install --agent codex and codex repair share one
  registration path. The local printDoctorChecks copy is dropped in
  favour of formatDoctorChecks from doctor.ts.
- capabilities: COMMAND_META gains an entry for `codex setup`.
- New ## Codex integration section after Quick start documents the
  full surface: prerequisites, npx-based one-command bootstrap (which
  also explains why the install-switchbot-cli step exists — it pins
  the global install after npx), manual install path, doctor/repair
  flow, --dry-run / --json / --yes / --skip flag matrix, and
  --profile / --config inheritance.
- Quick start gains a one-line callout redirecting Codex users away
  from the --agent claude-code default example.
- Table of contents links the new section.
Enable `"workspaces": ["packages/*"]` and import the Codex plugin from
the sibling `openclaw-switchbot-skill` repo into `packages/codex-plugin/`,
renamed from `@cly-org/switchbot-codex-plugin` to `@switchbot/codex-plugin`
(version reset to 0.1.0; old scope was never published — `npm view` 404).

CLI updates:
- src/install/codex-checks.ts: resolveCodexPackageRoot path now joins
  `@switchbot/codex-plugin`; doctor warning recipes reference the new name.
- src/install/preflight.ts and default-steps.ts: same rename.
- Test mocks updated; default plugin id derived from new dirname is now
  `switchbot@codex-plugin`.

Build / CI:
- Add aggregate scripts: `test:workspaces`, `test:all`, `typecheck:workspaces`,
  `typecheck:all`. Existing root `test`/`typecheck` keep root-only scope
  (preserves pre-commit/pre-push timings).
- ci.yml `test` job now runs `npm test` plus `npm run test:workspaces`.

Plugin import notes:
- Plain copy used instead of `git subtree add`. Sibling working tree had
  uncommitted in-flight test files that subtree split would have skipped.
  History remains in the sibling repo for archival lookup.
- Added `lib/` to `package.json#files`; sibling omitted it but
  `lib/error-messages.js` is a runtime import of `bin/auth.js`.
- Dropped 3 sibling-repo orchestration tests (codex-mcp-config,
  codex-setup, install-scripts) that imported `../../../scripts/*` —
  no analog in this monorepo; superseded by `switchbot codex setup`.

Spec decision log updated with the three execution-time deviations.
Hard checks pass: tarball has concrete peerDep `">=3.7.1"` (not
`workspace:*`), 11 files including `lib/error-messages.js`; scoped grep
for `@cly-org` in src/packages/.github/tests/scripts/README.md returns
zero hits.
Plain copy from sibling repo (per PR #1 deviation rationale — captures
in-flight uncommitted state that subtree split would miss).

Package identity:
- name: @switchbot/openclaw-skill (was @cly-org/switchbot-openclaw-skill)
- version: 0.1.0 (reset for first publish under new scope)
- repo URLs point at OpenWonderLabs/switchbot-openapi-cli
- author block stripped (top-level package.json has author)
- peerDep "@switchbot/openapi-cli": ">=3.7.1" (literal range, not workspace:*)
- files[] adds lib/ (matches PR #1 fix — runtime imports of error-messages.js)

Skipped @switchbot/agent-shared package entirely. The only candidate for
sharing is lib/error-messages.js (~30 lines of static error strings) and
codex's catalog is a strict superset of openclaw's. Bundling or publishing
a third package adds machinery without paying off; revisit if either
catalog grows materially. Documented in spec decision log.

Cleanup:
- codex-plugin: strip personal author/repo metadata, align with openclaw
- README: add OpenClaw integration section, update Quick start callout
- spec: log PR #2 deviations (plain copy, agent-shared skip)

Tests: codex-plugin 31/31, openclaw-skill 29/29, root vitest 2715/2715.
Restructure release pipeline now that @switchbot/codex-plugin and
@switchbot/openclaw-skill ship from this repo as workspace packages.

publish.yml: add detect-versions step that queries npm for each package,
publish only unpublished versions, gate plugin tarballs on concrete
peerDeps, set continue-on-error on plugin steps so plugin failures
surface as annotations without blocking CLI promotion.

npm-published-smoke.yml: per-package matrix (cli vs plugin kinds),
skip-if-not-republished logic, plugin tarball-shape checks (peerDep
literal, executable bin entries) without live smoke.

ci.yml: drop policy-schema-sync — the skill consumer is now in this
monorepo so the cross-repo sync gate is obsolete.

CHANGELOG: Unreleased entry documenting monorepo absorption, Codex
command group, plugin renames, and workflow restructure. No version
chosen yet — release decision is separate.
Keep packages/openclaw-skill/ in the monorepo (workspace tests still
cover it) but drop it from publish.yml and npm-published-smoke.yml
matrix. README OpenClaw integration section and CHANGELOG mentions
removed so users do not search for an unpublished npm package.

Re-enabling later only requires re-adding the publish + smoke entries.
…t-effort

- codex setup: add install-codex-plugin step between install-switchbot-cli
  and register-plugin so npx @switchbot/openapi-cli codex setup bootstraps
  end-to-end on a brand-new machine. Both install steps remain --skip-able.
- codex-plugin onInstall hook: always exits 0. When CLI is present it runs
  switchbot codex setup --yes; when absent it prints a hint and lets the
  Codex plugin install succeed (so a missing CLI never rolls it back).
- README + plugin README: 6-step list, single recommended npx path.
- Specs: align design docs with @SwitchBot scope and 6-step flow.

Tests: +1 root (codex setup install-codex-plugin coverage), +3 workspace
(makeRunOnInstall: missing CLI / setup fail / setup ok). 2716 root +
34 codex-plugin pass.
…e.json

- runCodexPluginRegistration now bridges packageRoot via a junction
  (Windows) or symlink (POSIX) in os.tmpdir() before invoking
  `codex plugin marketplace add`. codex CLI 0.133.0 misparses local
  paths containing `@` (e.g. <npm-root>/@switchbot/codex-plugin) as
  `owner/repo@ref` and rejects them with `--ref is only supported for
  git marketplace sources`. Falls back to the original path if the
  link cannot be created (test mocks, restricted filesystems).
- Track packages/codex-plugin/.agents/plugins/marketplace.json in git
  and add `.agents/` to the package `files` array so it ships in the
  npm tarball. The local source `path` is `../../` so codex resolves
  the plugin manifest at packageRoot/.codex-plugin/plugin.json (was
  `./`, which pointed at the wrong directory).
Pack the CLI and @switchbot/codex-plugin workspaces, install both
into a scratch dir, and assert tarball shape before any release:

- Plugin peerDependency is concrete (not workspace:*).
- Required files ship: .codex-plugin/plugin.json + hooks.json,
  .agents/plugins/marketplace.json, .mcp.json, skills/, bin/.
- marketplace.json name is "codex-plugin"; the switchbot plugin
  source.path is "../../" — codex resolves the path relative to
  .agents/plugins/marketplace.json itself, so it must climb two
  levels back to packageRoot/.codex-plugin/plugin.json.
- onInstall hook runs bin/auth.js --hook and exits 0.
- `switchbot codex setup --dry-run --json` lists the 6 setup steps.

Wired into prepublishOnly so a wrong path, missing files entry, or
broken hook is caught before the release tag.
…ationResult

resolveMarketplaceSourceRoot bridges scoped-npm packageRoots through
~/switchbot before calling `codex plugin marketplace add`. codex 0.133.0
mis-parses paths containing `@` (e.g. <root>\node_modules\@SwitchBot\
codex-plugin) as `owner/repo@ref` and rejects them. A previous attempt
aliased through os.tmpdir/<pid> and deleted the link in a finally —
codex persists the marketplace path, so the alias must outlive the
install. Reuse ~/switchbot when it already points at the same
packageRoot; only create the junction once.

runCodexPluginRegistration now sets stage: 'marketplace-add' |
'plugin-add' so registerCodexPlugin can name the failing codex
subcommand in its error (marketplace-add exit 1: ...). resolvePluginId
splits into resolvePluginName + resolveMarketplaceName so the
marketplace segment of the plugin id reads from .agents/plugins/
marketplace.json instead of basename(packageRoot).

publish.yml gains a smoke:codex-pack-install step (runs when the CLI
or codex-plugin version bumps). Tests cover the stage field, the
not-installed warn path, the marketplace.json name source, and the
Windows alias creation.
…unctions

resolveMarketplaceSourceRoot moves the @-path workaround junction from
~/switchbot to %LOCALAPPDATA%\switchbot\codex-plugin-marketplace
(fallback ~/.switchbot/codex-plugin-marketplace when LOCALAPPDATA is
unset). The previous location collided with user-owned directories
named "switchbot" and silently fell back to the broken @ path on any
mismatch. The new path is app-owned, so we treat divergent junctions
as repairable (unlink + recreate) and throw when a non-junction sits
at the alias path instead of returning a path Codex cannot parse. fs
errors propagate to the caller.

Tests cover four states: missing alias, healthy junction, stale
junction pointing elsewhere, and real-directory collision.
Mirrors the resolveMarketplaceSourceRoot rewrite from the CLI side:
junction lives at %LOCALAPPDATA%\switchbot\codex-plugin-marketplace
(fallback ~/.switchbot/...), divergent junctions are repaired in place,
and a real directory at the alias path throws instead of silently
returning the broken @ path. Adds a deps parameter for fs injection
so node:test can cover all four states without touching the real
filesystem.
chenliuyun added 6 commits May 23, 2026 20:09
Server listens on 127.0.0.1; on Node 18 (Linux), undici resolves
'localhost' to ::1 first and fails without IPv4 fallback, so CI
test (18.x) reports 'TypeError: fetch failed'. Match the bind
address explicitly to make the tests deterministic across Node
versions.
- publish.yml: add openclaw-skill version detection, unpublished check,
  and publish step (continue-on-error, provenance) mirroring codex-plugin
- preflight.ts: add shell: process.platform === 'win32' to the npm
  spawnSync call so .cmd shims are found on Windows (aligns with the
  existing pattern in codex-checks.ts)
- check-cli.js: add shell: SHELL to all execFile/execFileSync calls so
  switchbot.cmd and npm.cmd shims are launched correctly on Windows
Expose close() on CallbackHandle so callers can tear down the server on
demand. In browserLogin, wrap open() in try/catch and call close() on
failure — previously the port stayed occupied for the full 120 s timeout
if open() failed (e.g. headless / no-browser environments), blocking
immediate retries with EADDRINUSE.
…SS_DENIED

codex plugin-add backs up any existing installation before replacing it.
On Windows, if the previously registered plugin path is a junction with
restricted permissions, the backup hits os error 5 (ACCESS_DENIED).
Running plugin-remove first forces a fresh install, bypassing the backup.
runCodexPluginRegistration now calls plugin-remove before plugin-add;
update the four affected spawnSync mock sequences to match.
@chenliuyun chenliuyun merged commit aaadbf5 into main May 23, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant